Testing Pyramid and Scope
Understanding the testing pyramid helps you write effective tests.
Unit Tests
Fast, pure logic (parsers, validators, services without I/O), run in milliseconds.
Widget Tests
Verify a widget's UI and interaction in isolation (pumpWidget, find, tap, pumpAndSettle).
Integration Tests
Full app flows on device/emulator (user journeys, navigation, network stubs).
Rule: Many unit tests, fewer widget tests, minimal but strategic integration tests.
Test Setup and Tools
Proper tooling makes testing efficient and reliable.
Testing Tools
- Use the
flutter_testpackage (included). - Use mockito or mocktail for mocks; use test doubles (fakes) when behavior is simple.
- Use golden tests for visual regressions when UI consistency is critical.
- Use flutter_driver or integration_test (preferred) for end-to-end tests.
Add Dev Dependencies to pubspec.yaml (Conceptual)
dev_dependencies:
flutter_test:
mockito: # or mocktail
integration_test:
flutter_lints:
Unit Testing Patterns
Following patterns makes unit tests clear and maintainable.
Best Practices
- Test pure functions and model parsing with a variety of inputs including edge cases.
- Arrange-Act-Assert structure for clarity.
- Use setUp/tearDown for shared initialization.
Example Checks (Conceptual)
- Model.fromJson should parse valid JSON and throw FormatException on invalid JSON.
- Validator functions return expected error strings or null.
Best Practices
- Keep tests deterministic and fast.
- Avoid real network or file I/O; inject services and use mocks/fakes.
Widget Testing Basics
Widget tests verify UI behavior and interactions.
Widget Testing Process
- Pump the widget under test with required providers and dependencies.
- Use WidgetTester to interact (enterText, tap, pumpAndSettle).
- Assert visible text, widget counts, widget states, and navigation.
Common Patterns
- Wrap widgets with MaterialApp when navigation, themes, or localization are required.
- Provide mocked providers via Provider scope or by injecting fake services.
Example Flow
Pump HomeScreen with test provider returning sample items. Tap first item, await pumpAndSettle(), assert DetailsScreen content visible.
Integration Testing Essentials
Integration tests verify complete user journeys.
Integration Testing
- Write tests using integration_test package; run on emulator or real device.
- Drive full user journeys: login → navigate → perform action → verify API call or UI reaction.
- Use network stubs or run a local test server for reliable results.
Runner Pattern
Create an integration_test/main_test.dart that boots the app and uses the WidgetTester-like API to drive interactions.
Mocking and Test Doubles
Test doubles help isolate code under test.
Mocks
Assert interactions (method called with arguments). Use mocktail/mockito.
Fakes
Lightweight in-memory implementations (e.g., FakeCacheService using an in-memory map).
Stubs
Return canned responses for specific calls.
Guideline: Prefer fakes for stateful services to keep tests readable and reliable; use mocks when verifying interaction counts or arguments matters.
Test Organization and Naming
Well-organized tests are easier to maintain and understand.
Directory Structure
test/unit/— model and service teststest/widget/— widget testsintegration_test/— end-to-end tests
Naming Conventions
- Name tests to explain behavior:
should_parse_valid_json,shows_error_on_network_failure. - Keep test files parallel to lib structure for discoverability (e.g.,
test/models/course_test.dart).
Code Coverage and Metrics
Coverage metrics help identify untested code.
Generate Coverage
flutter test --coverage- Convert lcov.info to HTML with genhtml (CI artifact).
Set realistic coverage goals (e.g., 70–90%) and prioritize critical logic coverage over trivial getters.
Continuous Integration (CI) Basics
CI automates testing and quality checks.
CI Tasks
Static analysis, format check, unit & widget tests, integration tests (optional), build artifacts (apk/aab) for release channels.
CI Providers
Choose CI provider: GitHub Actions, GitLab CI, Bitrise, Codemagic. Example below uses GitHub Actions.
Core CI Steps
- Checkout code
- Install Flutter (version pinned)
- flutter pub get
- flutter analyze
- flutter test --coverage
- Build (optional): flutter build apk --release
- Upload artifacts and test reports
Example GitHub Actions Workflow (Conceptual)
GitHub Actions provides a straightforward CI/CD solution.
Workflow Configuration
- Trigger: push, pull_request on main branch.
- Jobs: analyze-and-test, build (optional), integration-tests (optional on matrix devices).
High-Level Job Steps
- Set up Flutter
- Run flutter pub get
- Run flutter analyze
- Run flutter test --coverage and upload coverage artifact
- Build APK and store as artifact
Notes: Use prebuilt actions for Flutter, cache pub packages, and fail fast on lint/test failures.
CI Best Practices and Flakiness Reduction
Following best practices ensures reliable CI pipelines.
Best Practices
- Keep tests deterministic: seed random values, avoid time-based assumptions, and use fake async when necessary.
- Split slow integration tests into a separate job that runs nightly or only on main branch merges.
- Cache dependencies to speed up pipelines.
- Upload test artifacts (logs, coverage, test reports) for debugging failures.
Automated Quality Gates
Quality gates enforce code standards automatically.
Quality Gates
- Enforce static analysis (flutter analyze) and lints via analysis_options.yaml.
- Fail PR pipelines if tests or analysis fail.
- Optionally add code coverage threshold checks and block merges when critical coverage drops.
Local Developer Workflow
Efficient local workflows catch issues early.
Local Testing
- Run tests frequently:
flutter test - Use
flutter test --coverageand a local coverage viewer or service to inspect. - Use pre-commit hooks to run format and basic checks (dart format, flutter analyze) to catch issues early.
Exercises
Practice what you've learned with these exercises:
1. Unit tests for models
Write tests for Course.fromJson with valid, missing fields, and malformed JSON.
2. Widget test for CourseList
Pump a CourseList with a fake provider, assert visible course cards, tap a card, and verify navigation intent.
3. CI pipeline (GitHub Actions)
Create a basic GitHub Actions workflow that runs analyze and tests on PRs. Commit workflow file and verify CI run on push.
Session Assignment
Implement and run the three exercises above. Add workflow YAML to repository, ensure the pipeline passes on your branch, and include a short CI results screenshot and test coverage lcov.info as part of your submission.